import os
import re
import subprocess
import logging
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
from typing import Dict, List, Tuple, Optional

class SymbolParser:
    """iOS崩溃堆栈符号解析器"""
    
    def __init__(self, dsym_path: str, arch: str = "arm64"):
        """
        初始化符号解析器
        
        Args:
            dsym_path: dSYM文件或包含多个dSYM文件的目录路径
            arch: 目标架构，默认为arm64
        """
        self.dsym_path = dsym_path
        self.arch = arch
        self.dsym_files = {}  # 存储模块名到dSYM文件路径的映射
        self.logger = logging.getLogger("SymbolParser")
        
        # 初始化解析器
        self._init_parser()
    
    def _init_parser(self):
        """初始化解析器，扫描dSYM文件"""
        if os.path.isfile(self.dsym_path):
            # 单个dSYM文件
            self._add_dsym_file(self.dsym_path)
        elif os.path.isdir(self.dsym_path):
            # 包含多个dSYM文件的目录
            self._scan_dsym_directory(self.dsym_path)
        else:
            self.logger.error(f"无效的dSYM路径: {self.dsym_path}")
            raise FileNotFoundError(f"无效的dSYM路径: {self.dsym_path}")
    
    def _scan_dsym_directory(self, directory: str):
        """
        扫描包含多个dSYM文件的目录
        
        Args:
            directory: 目录路径
        """
        self.logger.info(f"扫描dSYM目录: {directory}")
        
        # 查找所有.dSYM文件夹
        for root, dirs, files in os.walk(directory):
            for dir_name in dirs:
                if dir_name.endswith('.dSYM'):
                    dsym_path = os.path.join(root, dir_name)
                    self._add_dsym_file(dsym_path)
    
    def _add_dsym_file(self, dsym_path: str):
        """
        添加单个dSYM文件到解析器
        
        Args:
            dsym_path: dSYM文件路径
        """
        try:
            # 从dSYM文件名中提取模块名
            base_name = os.path.basename(dsym_path)
            if base_name.endswith('.dSYM'):
                module_name = base_name[:-5]  # 去除.dSYM后缀
                
                # 找到DWARF文件路径
                dwarf_path = os.path.join(dsym_path, 'Contents', 'Resources', 'DWARF', module_name)
                
                if os.path.exists(dwarf_path):
                    self.logger.info(f"找到dSYM文件: {module_name} -> {dwarf_path}")
                    self.dsym_files[module_name] = dwarf_path
                else:
                    # 尝试查找DWARF目录下的任何文件
                    dwarf_dir = os.path.join(dsym_path, 'Contents', 'Resources', 'DWARF')
                    if os.path.exists(dwarf_dir):
                        dwarf_files = os.listdir(dwarf_dir)
                        if dwarf_files:
                            real_module_name = dwarf_files[0]
                            dwarf_path = os.path.join(dwarf_dir, real_module_name)
                            self.logger.info(f"找到dSYM文件: {real_module_name} -> {dwarf_path}")
                            self.dsym_files[real_module_name] = dwarf_path
                            # 同时添加不带路径的模块名映射，方便匹配
                            short_name = os.path.splitext(real_module_name)[0]
                            if short_name != real_module_name:
                                self.dsym_files[short_name] = dwarf_path
        except Exception as e:
            self.logger.error(f"处理dSYM文件时出错: {dsym_path}, 错误: {str(e)}")
    
    def parse_stack_line(self, stack_line: str) -> str:
        """
        解析单行崩溃堆栈
        
        Args:
            stack_line: 崩溃堆栈行文本，如 "2 NC 0x0000000102671e8c detect_gwp_asanBU + 13569632"
            
        Returns:
            解析后的符号化文本
        """
        # 使用正则表达式解析堆栈行
        pattern = r'(\d+)\s+(\S+)\s+(0x[0-9a-fA-F]+)(?:\s+(\S+)(?:\s+\+\s+(\d+))?)?'
        match = re.match(pattern, stack_line.strip())
        
        if not match:
            return f"解析失败: 格式不匹配 - {stack_line}"
        
        index, module_name, address, image_name, offset = match.groups()
        
        # 检查模块名是否存在于dSYM文件列表中
        dsym_file = self._find_dsym_for_module(module_name)
        if not dsym_file:
            return f"解析失败: 未找到对应的dSYM文件 - {module_name}"
        
        try:
            # 提取基址(load_address)和偏移量
            base_addr = "0x0"  # 默认基址
            if image_name and offset:
                # 使用模块的加载地址和偏移量来确定实际地址
                # 这里需要根据实际崩溃堆栈格式进行调整
                pass
            
            # 构建atos命令
            atos_cmd = [
                'atos',
                '-o', dsym_file,
                '-arch', self.arch,
                '-l', base_addr,
                address
            ]
            
            # 执行atos命令
            result = subprocess.run(
                atos_cmd,
                capture_output=True,
                text=True,
                check=True
            )
            
            symbol = result.stdout.strip()
            if not symbol or "0x" in symbol:  # atos失败通常会返回原始地址
                return f"解析失败: 符号未找到 - {stack_line}"
            
            return f"{index} {module_name} {address} {symbol}"
        
        except subprocess.SubprocessError as e:
            error_msg = e.stderr if hasattr(e, 'stderr') else str(e)
            self.logger.error(f"atos命令执行失败: {error_msg}")
            return f"解析失败: atos命令错误 - {stack_line}"
        except Exception as e:
            self.logger.error(f"解析堆栈行时出错: {str(e)}")
            return f"解析失败: 内部错误 - {stack_line}"
    
    def _find_dsym_for_module(self, module_name: str) -> Optional[str]:
        """
        查找与模块名匹配的dSYM文件
        
        Args:
            module_name: 模块名称
            
        Returns:
            匹配的dSYM文件路径，未找到则返回None
        """
        # 直接匹配
        if module_name in self.dsym_files:
            return self.dsym_files[module_name]
        
        # 尝试不区分大小写匹配
        for name, path in self.dsym_files.items():
            if name.lower() == module_name.lower():
                return path
        
        # 尝试部分匹配（模块名可能是包名的一部分）
        for name, path in self.dsym_files.items():
            if module_name.lower() in name.lower() or name.lower() in module_name.lower():
                return path
        
        return None
    
    def parse_stack_lines(self, stack_lines: List[str], max_workers: int = 4) -> List[str]:
        """
        并行解析多行崩溃堆栈
        
        Args:
            stack_lines: 崩溃堆栈行列表
            max_workers: 最大并行工作线程数
            
        Returns:
            解析后的符号化文本列表
        """
        results = []
        
        # 使用线程池并行处理
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            results = list(executor.map(self.parse_stack_line, stack_lines))
        
        return results


# 使用示例
if __name__ == "__main__":
    # 示例代码
    dsym_dir = "/Users/user/Desktop/dSYMs"
    parser = SymbolParser(dsym_dir)
    
    stack_lines = [
        "2 NC 0x0000000102671e8c detect_gwp_asanBU + 13569632",
        "3 NC 0x00000001026717bc detect_gwp_asanBU + 13567888"
    ]
    
    results = parser.parse_stack_lines(stack_lines)
    for line in results:
        print(line) 